package org.liurb.springboot.starter.web.exception;

import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.extra.mail.MailAccount;
import cn.hutool.extra.mail.MailUtil;
import cn.hutool.json.JSONUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.ConstraintViolationException;
import lombok.extern.slf4j.Slf4j;
import org.liurb.springboot.starter.common.utils.RequestHttpUtil;
import org.liurb.springboot.starter.web.http.response.Result;
import org.liurb.springboot.starter.web.http.response.ResultEnum;
import org.liurb.springboot.starter.web.http.response.ResultUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.BindException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.client.RestClientException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;

import java.util.List;

/**
 * 公共异常处理
 *
 * 异常可通过邮件发送通知
 *
 * @Author Liurb
 * @Date 2022/11/28
 */
@Slf4j
public abstract class BackendGlobalExceptionHandler {

    @Resource
    MailAccount mailAccount;


    @ResponseBody
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Result methodArgumentNotValidExceptionHandler(HttpServletRequest httpServletRequest, MethodArgumentNotValidException e) {
        //打印第一条错误信息
        String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
        String exceptionText = "MethodArgumentNotValidException-请求参数格式异常";

        this.logger(httpServletRequest, exceptionText, message, e);

        return ResultUtil.fail(ResultEnum.PARAMS_ERROR.getCode(), message);
    }

    @ResponseBody
    @ExceptionHandler(BindException.class)
    public Result bindExceptionHandler(HttpServletRequest httpServletRequest, BindException e) {
        //打印第一条错误信息
        String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
        String exceptionText = "BindException-请求参数格式异常";

        this.logger(httpServletRequest, exceptionText, message, e);

        return ResultUtil.fail(ResultEnum.PARAMS_ERROR.getCode(), message);
    }


    @ResponseBody
    @ExceptionHandler(ConstraintViolationException.class)
    public Result constraintViolationExceptionHandler(HttpServletRequest httpServletRequest, ConstraintViolationException e){
        //打印第一条错误信息
        String message = e.getConstraintViolations().stream().findFirst().get().getMessage();
        String exceptionText = "ConstraintViolationException-请求参数格式异常";

        this.logger(httpServletRequest, exceptionText, message, e);

        return ResultUtil.fail(ResultEnum.PARAMS_ERROR.getCode(), ResultEnum.PARAMS_ERROR.getMsg());
    }

    @ResponseBody
    @ExceptionHandler(ServletException.class)
    public Result servletExceptionHandler(HttpServletRequest httpServletRequest, ServletException e) {
        String message = e.getMessage();
        String exceptionText = "ServletException-请求异常";

        this.logger(httpServletRequest, exceptionText, message, e);

        return ResultUtil.fail(ResultEnum.SYS_ERROR.getCode(), ResultEnum.SYS_ERROR.getMsg());
    }

    @ResponseBody
    @ExceptionHandler(RestClientException.class)
    public Result restClientExceptionHandler(HttpServletRequest httpServletRequest, RestClientException e) {
        String message = e.getMessage();
        String exceptionText = "RestClientException-调用异常";

        this.logger(httpServletRequest, exceptionText, message, e);

        //发送邮件
        if (isSendMail()) {
            this.sendMail(httpServletRequest, e.getMessage());
        }

        return ResultUtil.fail(ResultEnum.API_ERROR.getCode(), ResultEnum.API_ERROR.getMsg());
    }

    @ResponseBody
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public Result maxUploadSizeExceededExceptionHandler(HttpServletRequest httpServletRequest, MaxUploadSizeExceededException e) {
        String message = e.getMessage();
        String exceptionText = "MaxUploadSizeExceededException-文件大小超过限制异常";

        this.logger(httpServletRequest, exceptionText, message, e);

        return ResultUtil.fail(ResultEnum.SYS_ERROR.getCode(), "上传失败,文件大小超过限制");
    }

    @ResponseBody
    @ExceptionHandler(BackendException.class)
    public Result backendExceptionHandler(HttpServletRequest httpServletRequest, BackendException e) {
        String message = e.getMessage();
        String exceptionText = "BackendException-错误异常";

        Integer code = e.getCode();
        if (null == code) {
            code = ResultEnum.DEFAULT_FAIL.getCode();
        }

        this.logger(httpServletRequest, exceptionText, message, e);

        //发送邮件
        if (isSendMail() && e.isSendMail()) {
            this.sendMail(httpServletRequest, e.getMessage());
        }

        return ResultUtil.fail(code, e.getMessage());
    }

    @ResponseBody
    @ExceptionHandler(Exception.class)
    public Result exceptionHandler(HttpServletRequest httpServletRequest, Exception e) {
        String message = e.getMessage();
        String exceptionText = "Exception-未知异常";

        this.logger(httpServletRequest, exceptionText, message, e);

        //发送邮件
        if (isSendMail()) {
            this.sendMail(httpServletRequest, e.getMessage());
        }

        return ResultUtil.fail(ResultEnum.SYS_ERROR.getCode(), ResultEnum.SYS_ERROR.getMsg());
    }

    /**
     * 统一日志
     *
     * @param exceptionText
     * @param httpServletRequest
     * @param errorMessage
     * @param e
     */
    public void logger(HttpServletRequest httpServletRequest, String exceptionText, String errorMessage, Exception e) {
        String sessionId = httpServletRequest.getSession().getId();
        //get方法的请求参数
        String queryParams = JSONUtil.toJsonStr(RequestHttpUtil.getRequestQueryData(httpServletRequest));
        //post方法的请求参数
        String reqBody = JSONUtil.toJsonStr(RequestHttpUtil.getPostRequestData(httpServletRequest));
        log.error("{} 请求Url : {}, query : {}, body : {}, " + exceptionText + ": {}", sessionId, httpServletRequest.getRequestURI(), queryParams, reqBody, errorMessage, e);
    }

    /**
     * 发送邮件
     *
     * @param httpServletRequest
     * @param errorMsg
     */
    public void sendMail(HttpServletRequest httpServletRequest, String errorMsg) {
        //请求链接
        String requestUri = httpServletRequest.getRequestURI();
        //get方法的请求参数
        String queryParams = JSONUtil.toJsonStr(RequestHttpUtil.getRequestQueryData(httpServletRequest));
        //post方法的请求参数
        String reqBody = JSONUtil.toJsonStr(RequestHttpUtil.getPostRequestData(httpServletRequest));

        //发送邮件前置处理，可以制定发送次数的限制等
        boolean sendMailBefore = this.sendMailBefore(requestUri, errorMsg);
        if (!sendMailBefore) {
            return ;
        }

        //邮件内容
        String content = "path:" + requestUri + "<|>method:" + httpServletRequest.getMethod() + "<|>" + "query:" + queryParams + "<|>" + "body:" + reqBody + "<|>" + "errMsg:" + errorMsg;
        ThreadUtil.execute(() -> {
            //发送邮件
            MailUtil.send(mailAccount, tos(), subject(), content, false);
        });

        //发送邮件后的处理，可以用于记录发送邮件的次数，配合前置处理使用
        this.sendMailAfter(requestUri, errorMsg);
    }

    /**
     * 邮件收件人列表
     *
     * @return
     */
    public abstract List<String> tos();

    /**
     * 邮件标题
     *
     * @return
     */
    public abstract String subject();

    /**
     * 是否发送邮件
     *
     * @return
     */
    public abstract boolean isSendMail();

    /**
     * 发送邮件前处理
     *
     * @param requestUri
     * @param errorMsg
     * @return
     */
    public abstract boolean sendMailBefore(String requestUri, String errorMsg);

    /**
     * 发送邮件后处理
     *
     * @param requestUri
     * @param errorMsg
     * @return
     */
    public abstract void sendMailAfter(String requestUri, String errorMsg);

}
